{
    "cells": [
        {
            "attachments": {},
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "# design tips\n",
                "\n",
                "some anecdotal design considerations and examples / design-templates are provided here\n",
                "\n",
                "<*this document is a jupyter notebook - if they're new to you, check out how they work:\n",
                "[link](https://www.google.com/search?q=ipynb+tutorial),\n",
                "[link](https://jupyter.org/try-jupyter/retro/notebooks/?path=notebooks/Intro.ipynb),\n",
                "[link](https://colab.research.google.com/)*>\n",
                "\n",
                "*run all cells in this notebook in order (keep pressing shift+enter)*"
            ]
        },
        {
            "cell_type": "code",
            "execution_count": null,
            "metadata": {},
            "outputs": [],
            "source": [
                "if 'google.colab' in str(get_ipython()):\n  !pip install git+https://github.com/FullControlXYZ/fullcontrol --quiet\nimport fullcontrol as fc"
            ]
        },
        {
            "attachments": {},
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "#### intellisense and auto-complete\n",
                "\n",
                "FullControl objects and functions take advantage of intellisense and auto-complete\n",
                "- in jupyter lab, use tab and shift+tab for auto-complete and intellisense\n",
                "  - e.g. type 'fc.Poi' then tab then '(' then tab or shift+tab\n",
                "- in vscode, intellisense prompts pop-up automatically\n",
                "- check documentation for using intellisense in other software"
            ]
        },
        {
            "attachments": {},
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "#### use the 'travel_to' function for convenience\n",
                "\n",
                "the function fc.travel_to() is a convenient way to create a list of three steps: [Extruder(on=False), Point, Extruder(on=True)]"
            ]
        },
        {
            "cell_type": "code",
            "execution_count": null,
            "metadata": {},
            "outputs": [],
            "source": [
                "steps = []\n",
                "steps.append(fc.Point(x=0, y=0, z=0.2))\n",
                "steps.append(fc.Point(x=5, y=5))\n",
                "steps.extend([fc.Extruder(on=False), fc.Point(x=10, y=10), fc.Extruder(on=True)])\n",
                "steps.append(fc.Point(x=15, y=15))\n",
                "steps.extend(fc.travel_to(fc.Point(x=20, y=20)))\n",
                "steps.append(fc.Point(x=25, y=25))\n",
                "fc.transform(steps, 'plot')"
            ]
        },
        {
            "attachments": {},
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "#### for-loops and fc.move()\n",
                "\n",
                "layers are sometimes simple repetitions of the layer beneath, in which case, simply copying the layer's steps with fc.move() is useful (case 1 in the code below)\n",
                "\n",
                "a for-loop can be used instead (case 2)\n",
                "\n",
                "this allows other factors to be freely changed (case 3)"
            ]
        },
        {
            "cell_type": "code",
            "execution_count": null,
            "metadata": {},
            "outputs": [],
            "source": [
                "layers = 30\n",
                "\n",
                "# case 1:\n",
                "layer_steps = fc.rectangleXY(start_point=fc.Point(x=0,y=0,z=0.2), x_size=10, y_size=10)\n",
                "steps = fc.move(layer_steps, fc.Vector(z=0.2), copy=True, copy_quantity=layers)\n",
                "steps.insert(-2, fc.PlotAnnotation(label='case 1'))\n",
                "\n",
                "# travel to start of case 2\n",
                "steps.extend(fc.travel_to(fc.Point(x=20,y=0,z=0.2)))\n",
                "\n",
                "# case 2:\n",
                "for i in range(layers):\n",
                "    steps.extend(fc.rectangleXY(start_point=fc.Point(x=20,y=0,z=i*0.2), x_size=10, y_size=10))\n",
                "steps.insert(-2, fc.PlotAnnotation(label='case 2'))\n",
                "\n",
                "steps.extend(fc.travel_to(fc.Point(x=40,y=0,z=0.2)))\n",
                "\n",
                "# case 3: (x_size=10+i*0.2)\n",
                "for i in range(layers):\n",
                "    steps.extend(fc.rectangleXY(start_point=fc.Point(x=40,y=0,z=i*0.2), x_size=10+i*0.2, y_size=10))\n",
                "steps.insert(-2, fc.PlotAnnotation(label='case 3'))\n",
                "\n",
                "fc.transform(steps, 'plot')"
            ]
        },
        {
            "attachments": {},
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "#### tau\n",
                "\n",
                "tau equals 2*pi\n",
                "\n",
                "tau represents a full circle in radians, whereas pi represents half a circle\n",
                "\n",
                "the natural unit of measure is generally a full circle, not half a circle\n",
                "\n",
                "if you want an arc that is 3/4 of a circle, arc length can be written as any of the following:\n",
                "1. arc_length = 0.75 * tau\n",
                "1. arc_length = 1.5 * pi\n",
                "1. arc_length = 0.75 * 2 * pi\n",
                "\n",
                "0.75*tau is more natural and clearer\n",
                "\n",
                "consider the equivalent for units of years (similar to tau) or half-years (similar to pi). which of the following statements is clearer (all equivalent to the above three statements)?\n",
                "\n",
                "1. I'm going on holiday in three quarters of a year\n",
                "1. I'm going on holiday in one and a half half-years\n",
                "1. I'm going on holiday in three quarters of two half-years\n",
                "\n",
                "use tau!"
            ]
        },
        {
            "cell_type": "code",
            "execution_count": null,
            "metadata": {},
            "outputs": [],
            "source": [
                "from math import tau\n",
                "centre = fc.Point(x=0, y=0, z=0.2)\n",
                "steps = fc.arcXY(centre, 10, 0, 0.75*tau)\n",
                "fc.transform(steps, 'plot', fc.PlotControls(color_type='print_sequence'))"
            ]
        },
        {
            "attachments": {},
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "#### insert ***state***-changing instructions retrospectively\n",
                "\n",
                "e.g. turn extrusion on/off after creating multiple copies of geometry"
            ]
        },
        {
            "cell_type": "code",
            "execution_count": null,
            "metadata": {},
            "outputs": [],
            "source": [
                "steps = [fc.Point(x=i, y=i, z=0.2) for i in range(10)]\n",
                "steps.insert(4, fc.Extruder(on=False))\n",
                "steps.insert(8, fc.Extruder(on=True))\n",
                "fc.transform(steps,'plot')"
            ]
        },
        {
            "attachments": {},
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "#### use fc.PlotAnnotions() and fc.GcodeComments() to debug and communicate designs\n",
                "\n",
                "annotating the 3D plot is incredibly useful for communicating design intention or changes to state that aren't easy to show in a 3D geometric plot (e.g. fan speed)\n",
                "\n",
                "python's [f-strings](https://docs.python.org/3/tutorial/inputoutput.html#formatted-string-literals) are a useful tool to generate annotation strings parametrically"
            ]
        },
        {
            "cell_type": "code",
            "execution_count": null,
            "metadata": {},
            "outputs": [],
            "source": [
                "steps = []\n",
                "for i in range(13):\n",
                "    steps.append(fc.Point(x=i+1, y=i+1, z=0))\n",
                "    if i%2 == 0 and i<12:\n",
                "        steps.append(fc.Fan(speed_percent=i*10))\n",
                "        steps.append(fc.PlotAnnotation(label=f'fan speed {i*10}%'))\n",
                "fc.transform(steps, 'plot', fc.PlotControls(style='line', color_type='print_sequence'))"
            ]
        },
        {
            "attachments": {},
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "#### inspecting gcode\n",
                "\n",
                "aside from opening the gcode file in a text editor or gcode-preview software, you can print a range of lines to screen, or use any of python's built-in functions to inspect the text"
            ]
        },
        {
            "cell_type": "code",
            "execution_count": null,
            "metadata": {},
            "outputs": [],
            "source": [
                "output_type = 2 # change this to be 1, 2, or 3\n",
                "\n",
                "steps = [fc.Point(x=0,y=i,z=0) for i in range(11)]\n",
                "gcode = fc.transform(steps, 'gcode')\n",
                "gcode_list = (gcode.split('\\n'))\n",
                "if output_type == 1:\n",
                "    print(gcode)\n",
                "elif output_type == 2:\n",
                "    print('\\n'.join(gcode_list[5:8]))\n",
                "elif output_type == 3:\n",
                "    for gcode_line in (gcode_list):\n",
                "        if 'G1 F' in gcode_line or 'G0 F' in gcode_line:\n",
                "            print(gcode_line)"
            ]
        },
        {
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "#### concise point and relative-point definition\n",
                "\n",
                "use P and R functions from the fullcontrol lab for concise definition of points in a design:\n",
                "- absolute points (P) -> *steps.append(****P****(x,y,z))*\n",
                "- relative points (R) -> *steps.append(****R****(x,y,z))*\n",
                "\n",
                "fclab.setup_p() and fclab.setup_r() functions are used to create the P and R functions\n",
                "\n",
                "the setup_r() function must be passed the variable you are using for your list of steps. relative points are always defined relative to the last point in that list"
            ]
        },
        {
            "cell_type": "code",
            "execution_count": null,
            "metadata": {},
            "outputs": [],
            "source": [
                "import lab.fullcontrol as fclab\n",
                "\n",
                "steps = []\n",
                "P = fclab.setup_p()\n",
                "R = fclab.setup_r(steps)\n",
                "\n",
                "steps.append(P(40, 40, 0))\n",
                "steps.append(R(0, 1, 0))\n",
                "steps.append(R(1, 0, 0))\n",
                "\n",
                "for step in steps: print(step)"
            ]
        },
        {
            "attachments": {},
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "#### new geometry functions\n",
                "\n",
                "create your own geometry functions\n",
                "\n",
                "if you create useful geometry functions, add them to FullControl so everyone can benefit (see contribution guidelines on [github](https://github.com/FullControlXYZ/fullcontrol))\n",
                "\n",
                "also see the section later in this notebook about using AI to generate geometric functions"
            ]
        },
        {
            "cell_type": "code",
            "execution_count": null,
            "metadata": {},
            "outputs": [],
            "source": [
                "def saw_wave_x(start_point: fc.Point, length: float, amplitude: float, periods: int) -> list:\n",
                "    period_length = length/periods\n",
                "    steps_wave = []\n",
                "    for i in range(periods):\n",
                "        steps_wave.append(fc.Point(x=start_point.x+period_length*i, y=start_point.y, z=start_point.z))\n",
                "        steps_wave.append(fc.Point(x=start_point.x+period_length*i, y=start_point.y+amplitude, z=start_point.z))\n",
                "    steps_wave.append(fc.Point(x=start_point.x+length, y=start_point.y, z=start_point.z))   \n",
                "    return steps_wave\n",
                "                 \n",
                "steps = []\n",
                "steps.extend(saw_wave_x(fc.Point(x=20, y=20, z=0), 50, 10, 20))\n",
                "steps.extend(saw_wave_x(steps[-1], 50, 20, 10))\n",
                "steps.extend(saw_wave_x(steps[-1], 50, 10, 20))\n",
                "fc.transform(steps, 'plot', fc.PlotControls(color_type='print_sequence'))"
            ]
        },
        {
            "attachments": {},
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "#### assemble ***design-blocks*** to create a ***design***"
            ]
        },
        {
            "cell_type": "code",
            "execution_count": null,
            "metadata": {},
            "outputs": [],
            "source": [
                "centre = fc.Point(x=0, y=0, z=0.2)\n",
                "block1 = fc.spiralXY(centre, 0.5, 20, 0, 40, 2000)\n",
                "block2 = fc.helixZ(centre, 20, 0, 0, 60, 0.3, 2200)\n",
                "steps = block1 + block2\n",
                "fc.transform(steps, 'plot', fc.PlotControls(color_type='print_sequence'))"
            ]
        },
        {
            "attachments": {},
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "#### use fc.linspace() to create list of evenly-spaced numbers"
            ]
        },
        {
            "cell_type": "code",
            "execution_count": null,
            "metadata": {},
            "outputs": [],
            "source": [
                "print('e.g. \"fc.linspace(0,1,5)\": ' + str(fc.linspace(0,1,5)))\n",
                "from math import tau\n",
                "centre = fc.Point(x=0, y=0, z=0)\n",
                "point_count = 100\n",
                "radii = fc.linspace(10,20,point_count)\n",
                "angles = fc.linspace(0,tau*2,point_count)\n",
                "steps = [fc.polar_to_point(centre, radii[i], angles[i]) for i in range(point_count)]\n",
                "fc.transform(steps, 'plot', fc.PlotControls(color_type='print_sequence'))"
            ]
        },
        {
            "attachments": {},
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "#### polar_coordinates\n",
                "\n",
                "points can be generated based on a polar-coordinates definition\n",
                "\n",
                "state an origin, polar radius and polar angle\n",
                "\n",
                "the first two points in the code below are identical but defined by different methods"
            ]
        },
        {
            "cell_type": "code",
            "execution_count": null,
            "metadata": {},
            "outputs": [],
            "source": [
                "origin = fc.Point(x=0, y=0, z=0)\n",
                "from math import tau\n",
                "\n",
                "point_cart = fc.Point(x=10,y=0,z=0)\n",
                "print(point_cart)\n",
                "point_polar1 = fc.polar_to_point(origin, 10, 0)\n",
                "print(point_polar1)\n",
                "point_polar2 = fc.polar_to_point(origin, 10, tau/8)\n",
                "print(point_polar2)\n",
                "point_polar3 = fc.polar_to_point(origin, 10, tau/4)\n",
                "print(point_polar3)\n",
                "point_polar4 = fc.polar_to_point(origin, 10, -tau/4)\n",
                "print(point_polar4)"
            ]
        },
        {
            "attachments": {},
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "#### vase mode\n",
                "\n",
                "polar coordinates allow vase mode to be achieved easily"
            ]
        },
        {
            "cell_type": "code",
            "execution_count": null,
            "metadata": {},
            "outputs": [],
            "source": [
                "from math import cos, tau\n",
                "layers = 50\n",
                "segments_per_layer = 64\n",
                "centre = fc.Point(x=50, y=50, z=0)\n",
                "layer_height = 0.1\n",
                "steps = []\n",
                "for i in range(layers*segments_per_layer+1):\n",
                "    # find useful measures of completion\n",
                "    layer_fraction = (i%segments_per_layer)/segments_per_layer\n",
                "    total_fraction = (int(i/segments_per_layer)+layer_fraction)/layers\n",
                "    # calculate polar details\n",
                "    angle = layer_fraction*tau\n",
                "    radius = 5+1*cos(tau*(total_fraction))\n",
                "    centre.z = layer_height*layers*total_fraction\n",
                "    # add point\n",
                "    steps.append(fc.polar_to_point(centre, radius, angle))\n",
                "fc.transform(steps, 'plot', fc.PlotControls(zoom=0.8))"
            ]
        },
        {
            "attachments": {},
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "#### parametric maths equations with 't' (cartesian)\n",
                "\n",
                "use desmos to develop equations: [cartesian desmos link](https://www.desmos.com/calculator/2usosgsxtd)"
            ]
        },
        {
            "cell_type": "code",
            "execution_count": null,
            "metadata": {},
            "outputs": [],
            "source": [
                "from math import cos, tau\n",
                "x_size = 20\n",
                "y_offset, y_amplitude, waves = 5, 5, 3\n",
                "t_steps = fc.linspace(0, 1, 101) # [0, 0.01, 0.02, ... , 0.99, 1]\n",
                "steps = []\n",
                "for t_now in t_steps:\n",
                "    x_now = x_size*t_now\n",
                "    y_now = y_offset+y_amplitude*cos(t_now*tau*waves)\n",
                "    z_now = 0.2\n",
                "    steps.append(fc.Point(x=x_now, y=y_now, z=z_now))\n",
                "fc.transform(steps, 'plot', fc.PlotControls(color_type='print_sequence'))\n"
            ]
        },
        {
            "attachments": {},
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "#### parametric maths equations with 't' (polar)\n",
                "\n",
                "use desmos to develop equations: [polar desmos link](https://www.desmos.com/calculator/nropwukta4)"
            ]
        },
        {
            "cell_type": "code",
            "execution_count": null,
            "metadata": {},
            "outputs": [],
            "source": [
                "from math import cos, tau\n",
                "centre = fc.Point(x=0, y=0, z=0)\n",
                "inner_rad, rad_fluctuation, waves = 4, 1, 12\n",
                "t_steps = fc.linspace(0, 1, 1001)  # [0, 0.001, 0.002, ... , 0.999, 1]\n",
                "steps = []\n",
                "for t_now in t_steps:\n",
                "    a_now = t_now*tau\n",
                "    r_now = inner_rad+rad_fluctuation*cos(t_now*tau*waves)\n",
                "    z_now = 0\n",
                "    steps.append(fc.polar_to_point(centre, r_now, a_now))\n",
                "steps = fc.move(steps,fc.Vector(z=0.1),copy=True, copy_quantity=10)\n",
                "fc.transform(steps, 'plot')"
            ]
        },
        {
            "attachments": {},
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "#### 'post-process' a ***design*** or ***design-block***\n",
                "\n",
                "the following example creates a helical toolpath and then 'post-processes' it to change its geometry. the 'post-process' bit of code would work on different types of original geometry (e.g. a lattice-filled cylinder)"
            ]
        },
        {
            "cell_type": "code",
            "execution_count": null,
            "metadata": {},
            "outputs": [],
            "source": [
                "# create a basic simple geometry (a cylinder) that will be modified retrospectively\n",
                "centre = fc.Point(x=50, y=50, z=0)\n",
                "steps = fc.helixZ(centre, start_radius=10, end_radius=10, start_angle=0, n_turns=50, pitch_z=0.167, segments=50*64)\n",
                "steps.append(fc.PlotAnnotation(point = fc.Point(x=50, y=50, z=10), label='original geometry'))\n",
                "fc.transform(steps, 'plot', fc.PlotControls(color_type='print_sequence', zoom=0.7))\n",
                "\n",
                "# 'post-process' the geometry to change it\n",
                "z_max = 25\n",
                "for step in steps:\n",
                "    if isinstance(step, fc.Point):\n",
                "        step.x -= 0.8*(step.x-centre.x)*(step.z/z_max)\n",
                "        step.y -= 0.8*(step.y-centre.y)*(step.z/z_max)\n",
                "        step.z -= (((step.y-centre.y)/2.5)**2)*(step.z/z_max)\n",
                "steps[-1] = fc.PlotAnnotation(point = fc.Point(x=50, y=50, z=10), label=\"'postprocessed' geometry\")\n",
                "fc.transform(steps, 'plot', fc.PlotControls(color_type='print_sequence', zoom=0.7))"
            ]
        },
        {
            "attachments": {},
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "the following code cell shows a slightly more complex 'post-process', where a linear wave is created and then 'wrapped' around a cylinder to form an arc\n",
                "\n",
                "this may be useful if it's easier to define some particular geometry in a linear format than in a curved format"
            ]
        },
        {
            "cell_type": "code",
            "execution_count": null,
            "metadata": {},
            "outputs": [],
            "source": [
                "from math import tau\n",
                "rad, rad_fluc, segs_per_period = 20, 5, 12\n",
                "periods = 25\n",
                "period_length = 3  # calculated to make sure the wave is the length of the circle circumference\n",
                "steps = fc.sinewaveXYpolar(fc.Point(x=rad, y=0, z=0.2), 0.75*tau, rad_fluc, period_length, periods, segs_per_period)\n",
                "steps.append(fc.PlotAnnotation(point=fc.Point(x=1.5*rad, y=-60, z=10), label='original geometry'))\n",
                "fc.transform(steps, 'plot', fc.PlotControls(color_type='print_sequence'))\n",
                "\n",
                "def linear_to_arc(steps: list, centre: fc.Point, radius: float) -> list:\n",
                "    '''this function assumes the linear geometry (list of points: 'steps') is oriented in \n",
                "    the y direction and positioned in the positive x-direction from the centre point. it \n",
                "    is 'wrapped' around an arc/circle dictated by radius. it is possible to wrap multiple \n",
                "    times. return list of translated points\n",
                "    '''\n",
                "    steps_wrapped = []\n",
                "    for step in steps:\n",
                "        rad_step = step.x - centre.x\n",
                "        angle_step = (step.y - centre.y) / radius\n",
                "        steps_wrapped.append(fc.polar_to_point(centre, rad_step, angle_step))\n",
                "    return steps_wrapped\n",
                "\n",
                "del steps[-1] # remove the PlotAnnotation\n",
                "steps_wrapped = linear_to_arc(steps, fc.Point(x=0, y=0, z=0.2), 15)\n",
                "steps_wrapped.append(fc.PlotAnnotation(point=fc.Point(x=0, y=0, z=10), label=\"'post-processed' geometry\"))\n",
                "\n",
                "fc.transform(steps_wrapped, 'plot', fc.PlotControls(color_type='print_sequence'))"
            ]
        },
        {
            "attachments": {},
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "#### generation speed\n",
                "\n",
                "don't worry too much about speed - future enhancements to the FullControl source code can easily:\n",
                "\n",
                "- increase speed all-round\n",
                "- allow quick previews\n",
                "- include lightweight variants of objects with less functionality but greater speed\n",
                "- explain how to generate designs quickly for specific ***results*** - e.g. no 'plot' option for fc.transform(), but greater gcode-generation speed\n",
                "- explain how to create custom versions of FullControl for specific applications that only include strictly necessary functionality\n",
                "\n",
                "immediate opportunities to increase the speed of plot previews:\n",
                "\n",
                "- use ***design-blocks*** (described above) - create and preview them one at a time\n",
                "- reducing segments for the 'plot' result but not the 'gcode' result\n",
                "- increasing layer height to be unrealistically high for the 'plot' result but not the 'gcode' result\n",
                "- create your design such that frequently changed ***state***-changing objects that don't affect the plot are not created when creating a ***design*** (see the code-block below)\n",
                "    \n",
                "- for ***designs*** with lots of travel, create your design such that travel controls (fc.Extruder(on=###)) do not execute when creating a ***design*** for a 'plot' ***result***\n",
                "    - this means plotly plots the whole design as a single line series (fast), as opposed to lots of individual line series (slow)\n"
            ]
        },
        {
            "cell_type": "code",
            "execution_count": null,
            "metadata": {},
            "outputs": [],
            "source": [
                "result_type = 'plot'  # set as 'gcode' or 'plot'\n",
                "steps = []\n",
                "for i in range(6):\n",
                "    if result_type != 'plot':\n",
                "        # fan speed does not affect the 'plot' result so doesn't need to be added to the design result_type == 'plot'\n",
                "        steps.append(fc.Fan(speed_percent=100*i/5))\n",
                "    steps.append(fc.Point(x=5*(i+1), y=5*((i+1)%2), z=0.2))\n",
                "print(f\"the design specifically for the '{result_type}' result contains {len(steps)} steps:\")\n",
                "for step in steps: print(type(step).__name__)\n",
                "fc.transform(steps, result_type, fc.PlotControls(color_type='print_sequence'))"
            ]
        },
        {
            "attachments": {},
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "#### use AI to create designs or geometry functions\n",
                "\n",
                "this is most suitable for users who have already got a good understanding of how to use FullControl since there is a reasonable risk that AI-generated code will not exactly match FullControl's requirements (e.g. it may generate a numpy array, or a dict instead of a list of FullControl Point objects)\n",
                "\n",
                "example [chatGPT](https://chat.openai.com/) request:\n",
                "```\n",
                "In python, a pydantic BaseModel class for a point has been imported in a module named fc\n",
                "\n",
                "Create a list of points for a spiral with defined start radius, end radius, turns, number of points and centre point\n",
                "```\n",
                "\n",
                "the chatGPT output is given below. it is very sensitive to the question and can generate different responses when asked the same question twice. for the result generated below, chatGPT assumed the Point class was imported from the fc module rather than importing the whole fc module as done in these tutorial notebooks. therefore it doesn't use the fc.Point terminology, it just uses Point. a reasonable level of python knowledge is required to ensure the output can be tweaked as needed to work with FullControl as well as a good understanding of FullControl. E.g. this chatGPT code has no z values, but at least one point in your design must have a z value to allow a 3D plot\n",
                "\n",
                "chatGPT output:\n",
                "\n",
                "```\n",
                "from math import cos, sin, pi\n",
                "from fc import Point\n",
                "from typing import List\n",
                "\n",
                "def generate_spiral_points(start_radius: float, end_radius: float, turns: int, num_points: int, center: Point) -> List[Point]:\n",
                "    points = []\n",
                "    radius_diff = end_radius - start_radius\n",
                "    angle_step = 2 * pi * turns / num_points\n",
                "    for i in range(num_points):\n",
                "        radius = start_radius + radius_diff * i / (num_points - 1)\n",
                "        angle = i * angle_step\n",
                "        x = center.x + radius * cos(angle)\n",
                "        y = center.y + radius * sin(angle)\n",
                "        points.append(Point(x=x, y=y))\n",
                "    return points\n",
                "```\n"
            ]
        },
        {
            "cell_type": "code",
            "execution_count": null,
            "metadata": {},
            "outputs": [],
            "source": [
                "from math import cos, sin, pi\n",
                "\n",
                "def generate_spiral_points(start_radius: float, end_radius: float, turns: int, num_points: int, center: fc.Point) -> list:\n",
                "    points = []\n",
                "    radius_diff = end_radius - start_radius\n",
                "    angle_step = 2 * pi * turns / num_points\n",
                "    for i in range(num_points):\n",
                "        radius = start_radius + radius_diff * i / (num_points - 1)\n",
                "        angle = i * angle_step\n",
                "        x = center.x + radius * cos(angle)\n",
                "        y = center.y + radius * sin(angle)\n",
                "        points.append(fc.Point(x=x, y=y, z=center.z)) # added 'fc.' and a z value is required for FullControl to create a plot in 3D space\n",
                "    return points\n",
                "\n",
                "steps = generate_spiral_points(10, 20, 4, 128, fc.Point(x=50, y=50, z=0))\n",
                "fc.transform(steps, 'plot', fc.PlotControls(color_type='print_sequence'))"
            ]
        }
    ],
    "metadata": {
        "kernelspec": {
            "display_name": "Python 3 (ipykernel)",
            "language": "python",
            "name": "python3"
        },
        "language_info": {
            "codemirror_mode": {
                "name": "ipython",
                "version": 3
            },
            "file_extension": ".py",
            "mimetype": "text/x-python",
            "name": "python",
            "nbconvert_exporter": "python",
            "pygments_lexer": "ipython3",
            "version": "3.11.3"
        },
        "vscode": {
            "interpreter": {
                "hash": "2b13a99eb0d91dd901c683fa32c6210ac0c6779bab056ce7c570b3b366dfe237"
            }
        }
    },
    "nbformat": 4,
    "nbformat_minor": 4
}
